API Gateway カスタムオーソライザーの挙動を確認してみた
こんにちは hagiwara です。
API Gateway でカスタムオーソライザーを使用しているのですが、設定方法や挙動の疑問点があったので、実際に動かして確認してみました。
API Gateway REST API で、Lambda オーソライザーを使用した場合を対象としています。
Lambda オーソライザーのインプット
オーソライザーへのインプットは Token と Request の2タイプあります。
Token タイプ
HTTP ヘッダに設定されたトークンの値をインプットとして検証を行います。
Token Source
に任意のヘッダ名を設定します。Authorization ヘッダを使用するのが一般的だと思います。
インプットイベント
Lambda のインプットイベントは以下のようになりました。
authorizationToken
には、Token Source
に設定したヘッダの値が格納されます。
{ "type": "TOKEN", "methodArn": "arn:aws:execute-api:[region]:[account-id]:[api-id]/[stage]/[http-method]/[resource-path]", "authorizationToken": "token-value" }
トークンが存在しない場合
トークンに該当するヘッダが存在しないリクエストの場合、オーソライザーは実行されませんでした。
その場合のレスポンスは、ゲートウェイレスポンスのレスポンスタイプUNAUTHORIZED
になりました。
レスポンスの内容を変更するには、このタイプのテンプレートの修正する形になります。
コンテキストのエラーメッセージ$context.error.messageString
は"Unauthorized"
でした。
{ "message" : "Unauthorized" }
トークンの検証
Token タイプのオーソライザーにはToken Validation
に正規表現を設定することで、トークンを検証できます。
この項目を設定し、正規表現にマッチしない場合、オーソライザーは実行されませんでした。
設定した条件にマッチしなかった場合のレスポンスも、トークンが存在しない場合と同様の挙動となりました。
Request タイプ
HTTPリクエスト全体の値をインプットにして検証を行います。
インプットイベント
Lambda のインプットになるイベントは以下のようになりました。
リクエスト全体の値を取得できています。
{ "type": "REQUEST", "methodArn": "arn:aws:execute-api:[region]:[account-id]:[api-id]/[stage]/[http-method]/[resource-path]", "resource": "/[resource-path]", "path": "/[resource-path]", "httpMethod": "[http-method]", "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate, br", "Cache-Control": "no-cache", "Content-Type": "application/json", "Host": "[api-id].execute-api.[region].amazonaws.com", "User-Agent": "PostmanRuntime/7.32.2", "X-Amzn-Trace-Id": "Root=1-644cb805-2e9d2d8c4ea4bac444b3729b", "X-Forwarded-For": "[source-ip]", "X-Forwarded-Port": "443", "X-Forwarded-Proto": "https" }, "multiValueHeaders": { "Accept": [ "*/*" ], "Accept-Encoding": [ "gzip, deflate, br" ], "Cache-Control": [ "no-cache" ], "Content-Type": [ "application/json" ], "Host": [ "[api-id].execute-api.[region].amazonaws.com" ], "User-Agent": [ "PostmanRuntime/7.32.2" ], "X-Amzn-Trace-Id": [ "Root=1-644cb805-2e9d2d8c4ea4bac444b3729b" ], "X-Forwarded-For": [ "[source-ip]" ], "X-Forwarded-Port": [ "443" ], "X-Forwarded-Proto": [ "https" ] }, "queryStringParameters": {}, "multiValueQueryStringParameters": {}, "pathParameters": {}, "stageVariables": {}, "requestContext": { "resourceId": "[resource-id]", "resourcePath": "/[resource-path]", "httpMethod": "[http-method]", "extendedRequestId": "EIGw9F6ANjMF-5Q=", "requestTime": "29/Apr/2023:06:24:05 +0000", "path": "/[stage]/[resource-path]", "accountId": "[account-id]", "protocol": "HTTP/1.1", "stage": "[stage]", "domainPrefix": "[api-id]", "requestTimeEpoch": 1682749445879, "requestId": "28def2c5-288a-45cc-814f-dfbe854fc198", "identity": { "cognitoIdentityPoolId": null, "accountId": null, "cognitoIdentityId": null, "caller": null, "sourceIp": "[source-ip]", "principalOrgId": null, "accessKey": null, "cognitoAuthenticationType": null, "cognitoAuthenticationProvider": null, "userArn": null, "userAgent": "PostmanRuntime/7.32.2", "user": null }, "domainName": "[api-id].execute-api.[region].amazonaws.com", "apiId": "[api-id]" } }
キャッシュを設定する場合
Request タイプのオーソライザーの検証結果をキャッシュする場合、キャッシュのキーとなるIdentity Sources
の値を設定する必要があります。
ヘッダやクエリストリングなど、複数のキーを設定することが可能です。
Identity Sources
が設定されており、リクエストにその値が存在しない場合、オーソライザーは実行されませんでした。この場合も、トークンが存在しない場合と同様、レスポンスタイプUNAUTHORIZED
になりました。
Lambda オーソライザーのアウトプット
オーソライザーのアウトプットは以下の3タイプを Lambda から返却することができます。
その結果、挙動は4パターンとなります。
- Lambda 正常終了 (ポリシーを返却)
- ポリシーを評価した結果 Allow だった場合 → 後続のリクエスト処理を実行
- ポリシーを評価した結果 Allow ではなかった場合 →
ACCESS_DENIED
- Lambda エラー終了 (Unauthorized) →
UNAUTHORIZED
- Lambda エラー終了 (Unauthorized以外) →
AUTHORIZER_FAILURE
Lambda 正常終了
Lambda から以下のようなレスポンスを返却します。
{ "principalId": "[user-id]", "policyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": "execute-api:Invoke", "Effect": "Allow|Deny", "Resource": "arn:aws:execute-api:[region]:[account-id]:[api-id]/[stage]/[http-method]/[resource-path]" } ] }, "context": { "[key]": "[value]", }, "usageIdentifierKey": "[api-key]" }
policyDocument.Statement
の内容を評価した結果、対象のリソースが許可されていた場合、後続のリクエスト処理が実行されます。
許可されていなかった場合は、レスポンスタイプACCESS_DENIED
になりました。
コンテキストのエラーメッセージ$context.error.messageString
は以下でした。
"User is not authorized to access this resource with an explicit deny"
オーソライザーのキャッシュを使用していない場合は、受け取ったmethodArn
に対する、Allow または Deny を返す形で問題ないと思います。
キャッシュを使う場合は、キャッシュのキー (Token Source, Identity Sources) に対して、オーソライザーが設定されているすべてのリソースを網羅したステートメントのリストを返す必要があります。
詳細はいわささんのブログを参照してください。
Statement を空配列にしてみたところ、レスポンスタイプAUTHORIZER_CONFIGURATION_ERROR
になりました。Allow を設定しない場合は Deny を設定しておく必要がありそうです。
policyDocument
以外の項目は、ない場合もエラーにはなりませんでした。
principalId
およびcontext
に設定した値は、後続のリクエスト処理および、レスポンスタイプACCESS_DENIED
のテンプレートから参照可能です。
Lambda エラー終了 (Unauthorized)
Lambda が "Unauthorized"
のエラーを返却した場合、レスポンスタイプUNAUTHORIZED
のレスポンスとなります。
エラーメッセージ$context.error.messageString
は"Unauthorized"
となっていました。
Nodeの実装例ではcallback("Unauthorized")
となっています。
一部しか試せてませんが、他のランタイムの場合もエラーの文字列表現を"Unauthorized"
にすることで同様の結果になりました。
"Unauthorized"
は大文字小文字の区別をしているようで、"unauthorized"
や"UNAUTHORIZED"
の場合は判定されませんでした。
Lambda エラー終了 (Unauthorized 以外)
Lambda が"Unauthorized"
以外のエラーを返却した場合、レスポンスタイプAUTHORIZER_FAILURE
になりました。
エラーメッセージ$context.error.messageString
はnull
になっていました。
その他
インプットアウトプット以外に試してみた挙動を記載します。
Lambda エラー終了はキャッシュされるか
Lambda オーソライザーがエラーを返した場合、その結果はキャッシュされませんでした。
インプット (Tokenタイプ, Requestタイプ)、アウトプット(Unauthorized, その他のエラー)、の組み合わせを試してみましたが、すべての場合でキャッシュはされず、リクエストの度にオーソライザーが実行されました。
Lambda オーソライザーキャッシュの消し方
CLI から flush-stage-authorizers-cache を実行することで、キャッシュをクリアすることができました。
aws apigateway flush-stage-authorizers-cache --rest-api-id [api-id] --stage-name [stage]
API Key が不正な場合オーソライザーは実行されるか
実行されました。
API Gateway の機能である API キーを必須とし、対象のx-api-key
ヘッダが不正な場合や存在しない場合を試してみましたが、API キーの検証の前にオーソライザーは実行されるようです。
おわりに
以上、Lambda オーソライザーの動きを確認してみました。
ドキュメントからは読み取りづらい場合もありますが、簡単に試してみることができるので、迷ったら簡単なサンプルを作って動かしてみるのが早くて確実かもしれません。